/*
 * llds.cpp
 *
 *  Created on: Jul 14, 2009
 *      Author: azoulaii
 */

#include "llds.h"

LowLevelDiskServices::LowLevelDiskServices(Disk *d,FreeBlockManager *bmanager,InodeManager *imanager){
	this->_block_manager = bmanager;
	this->_disk = d;
	this->_inode_manager = imanager;
	createRoot();
}

int LowLevelDiskServices::allocateInode(){
	inode *node = this->createInode();
	int inode_number = this->_inode_manager->pop();
	this->_disk->write_inode(inode_number,(char*)node);

	return inode_number;
}

inode *LowLevelDiskServices::createInode(){
	inode *node = new inode;
	node->itype = NoType;
	node->file_size = 0;
	node->in_direct = NONE;
	node->num_of_hard_link = 0;
	for(int i = 0; i < NUM_OF_DIRECT;++i)
		node->direct[i] = NONE;
	node->in_direct = NONE;

	return node;
}

int LowLevelDiskServices::allocateDataBlock(){
	return this->_block_manager->pop();
}

void LowLevelDiskServices::freeDataBlock(int block_number){
	char zero[this->_disk->getBlockSize()];
	for(int i = 0; i < this->_disk->getBlockSize(); ++i)
		zero[i] = 0;
	this->_disk->write_block(block_number,zero);
	this->_block_manager->push(block_number);
}

type LowLevelDiskServices::getInodeType(int i_number){
	inode *iNode = (inode*)this->_disk->read_inode(i_number);
	return iNode->itype;
}

void LowLevelDiskServices::setInodeType(int i_number,type file_type){

	inode *iNode = (inode*)this->_disk->read_inode(i_number);

	iNode->itype = file_type;

	this->_disk->write_inode(i_number,(char*)iNode);

}

void LowLevelDiskServices::setDataBlock(int i_number,int i,int d_block_num){
	inode *iNode = (inode*)this->_disk->read_inode(i_number);
	general_data *g = this->_disk->read_general_data();

	if(i >= 0 && i <= 9)
		iNode->direct[i] = d_block_num;
	else if(i > 9 && (unsigned int)i <= g->blk_size/sizeof(int)){

		int *in_direct = (int*)this->_disk->read_block(iNode->in_direct);
		in_direct[i - 9] = d_block_num;
		this->_disk->write_block(iNode->in_direct,(char*)in_direct);

	}else{
		cout<<"No such block"<<endl;
	}
	this->_disk->write_inode(i_number,(char*)iNode);
}

int  LowLevelDiskServices::getDataBlock(int i_number,int i){
	inode *iNode = (inode*)this->_disk->read_inode(i_number);
	general_data *g = this->_disk->read_general_data();

	if(i >= 0 && i <= 9)
		return iNode->direct[i];
	else if(i > 9 && (unsigned int)i <= g->blk_size/sizeof(int)){
		int *in_direct = (int*)this->_disk->read_block(iNode->in_direct);
		return in_direct[i - 9];
	}else{
		cout<<"No such block"<<endl;
		return -1;
	}
}

void LowLevelDiskServices::readBlock(int block_number,char *buffer){
	this->_disk->read_block_to_buffer(block_number,buffer);
}

void LowLevelDiskServices::writeBlock(int block_number,char *new_data){
	this->_disk->write_block(block_number,new_data);
}

int LowLevelDiskServices::getFileSize(int i_number){
	inode *iNode = (inode*)this->_disk->read_inode(i_number);
	return iNode->file_size;
}
void LowLevelDiskServices::addBlocks(inode* iNode,int blocks){
	general_data *g = this->_disk->read_general_data();
	int num_of_blocks = 0;
	if(iNode->file_size > 0)
		num_of_blocks = iNode->file_size/g->blk_size + 1;
	int i = num_of_blocks;
	for(; i < blocks && i < NUM_OF_DIRECT;++i){
		iNode->direct[i] = _block_manager->pop();
	}
	if(blocks >= NUM_OF_DIRECT){
		if(iNode->in_direct == NONE)
			iNode->in_direct = _block_manager->pop();
		int *pointers = (int*)this->_disk->read_block(iNode->in_direct);
		for(;i < blocks;++i)
			pointers[i - NUM_OF_DIRECT] = _block_manager->pop();

		this->_disk->write_block(iNode->in_direct,(char*)pointers);
	}

}

void LowLevelDiskServices::reduceBlocks(inode* iNode,int blocks){
	general_data *g = this->_disk->read_general_data();
	int num_of_blocks = iNode->file_size/g->blk_size + 1;
	int i = blocks;
	for(; i < num_of_blocks && i < NUM_OF_DIRECT; ++i){
		freeDataBlock(iNode->direct[i]);
		iNode->direct[i] = NONE;
	}
	if(num_of_blocks >= NUM_OF_DIRECT){
		if(iNode->in_direct == NONE)
			return;
		int *pointers = (int*)this->_disk->read_block(iNode->in_direct);
		for(;i < num_of_blocks;++i){
			freeDataBlock(pointers[i - NUM_OF_DIRECT]);
			pointers[i - NUM_OF_DIRECT] = -1;
		}

		this->_disk->write_block(iNode->in_direct,(char*)pointers);
		iNode->in_direct = NONE;
	}
}
void LowLevelDiskServices::setFileSize(int i_number,int new_size){
	inode *iNode = (inode*)this->_disk->read_inode(i_number);
	general_data *g = this->_disk->read_general_data();

	int new_num_of_blocks = new_size/g->blk_size + 1;
	if(new_size < iNode->file_size){
		this->reduceBlocks(iNode,new_num_of_blocks);
	}else{
		this->addBlocks(iNode,new_num_of_blocks);
	}
	iNode->file_size = new_size;
	this->_disk->write_inode(i_number,(char*)iNode);
}

void LowLevelDiskServices::freeInode(int i_number){
	inode *iNode = (inode*)this->_disk->read_inode(i_number);
	for(int i = 0; i <NUM_OF_DIRECT && iNode->direct[i] != NONE; ++i)
	//	this->_block_manager->push(iNode->direct[i]);
		this->freeDataBlock(iNode->direct[i]);
	if(iNode->in_direct != NONE){
		int *pointers = (int *)this->_disk->read_block(iNode->in_direct);
		for(int i = 0; i <16 && pointers[i] != NONE; ++i)
			//this->_block_manager->push(pointers[i]);
			this->freeDataBlock(pointers[i]);
	}

	int *pInode = (int *)iNode;
	for(unsigned int i = 0; i < I_NODE_SIZE/sizeof(int); ++i)
		pInode[i] = 0;
	this->_inode_manager->push(i_number);
	this->_disk->write_inode(i_number,(char*)iNode);
}

general_data* LowLevelDiskServices::getGeneralData(){
	return this->_disk->read_general_data();
}

void LowLevelDiskServices::createRoot(){
	general_data *g = this->getGeneralData();
	if(g->root_iNode != NONE)
		return;
	int root = this->allocateInode();
	this->setInodeType(root,Directory);
	g->root_iNode = root;

	this->_disk->writeGeneralData(g);

}
